#ifndef REMOTECLIENT_H
#define REMOTECLIENT_H

#include <QtNetwork/QtNetwork>
#include <QThread>
#include <QString>

#include "Packet.h"
#include "monitor.h"

namespace Network {

class RemoteClient;

typedef QSharedPointer<RemoteClient> pRemoteClient;

/**
 * @brief Klasa reprezentująca połączenie z innym komputerem.
 *
 * Stosowana zarówno po stronie klienta jak i serwera, umożliwia wysyłanie
 * i odbieranie pakietów na gnieździe przypisanym do tego połaczenia.
 * Zdarzenia związane z pakietami (zakończenie wysyłki, odbiór nowego pakietu)
 * są sygnalizowane na zewnątrz za pomoca odpowiednich sygnałów.
 *
 * RemoteClient jest pierwszym etapem na drodze pakietu, surowe dane są przez
 * niego zczytywane z gniazda, formowany jest obiekt typu Packet a nastepnie
 * przekazywany jest on dalszym modułom.
 **/
class RemoteClient : public QThread
{
	Q_OBJECT

public:
	/**
	 * Utworzenie nowego połączenia korzystającego z podanego gniazda.
	 *
	 * @param sock gniazdo przypisane do połączenia
	 * @param id numer identyfikacyjny klienta
	 **/
	RemoteClient(QTcpSocket * sock, int id, QObject * parent = 0);

	~RemoteClient();

	/**
	 * @return sformatowany adres drugiego końca połączenia
	 **/
	QString getAddr();

	/**
	 *
	 **/
	QHostAddress getHostAddress() {
		return addr_;
	}

	/**
	 * @return gniazdo przypisane do danego klienta
	 **/
	QTcpSocket * socket() {
		return sock_;
	}

	/**
	 * @return identyfikator klienta
	 **/
	int id() {
		return id_;
	}

	void id(int new_id) {
		id_ = new_id;
	}

	/**
	 * Zakończenie wątku danego klienta
	 **/
	void stop();

	/**
	 * Wysłanie do danego klienta paczki danych. Dane są kolejkowane
	 * i wysyłane przy najbliższej nadarzającej się okazji.
	 *
	 * @param packet wskaźnik na pakiet do wysłania
	 **/
	void send(Packet * packet);

	/**
	 * Zdjęcie pakietu z kolejki odebranych. Kontrolę nad zwolnieniem pamięci
	 * zajmowanej przez ten pakiet przejmuje funkcja ściągająca pakiet z kolejki.
	 *
	 * @return wskaźnik na pierwszy pakiet w kolejce bądź NULL w przypadku kolejki pustej.
	 **/
	Packet * recv();

	int listeningAtPort() {
		return listening_port_;
	}

	void listeningAtPort(int p) {
		listening_port_ = p;
	}

private:
	/**
	 * Funkcja uruchamiana automatycznie podczas uruchamiania wątku klienta
	 **/
	void run();

	/// gniazdo związane z danym połaczeniem
	QTcpSocket * sock_;

	/// numer identyfikacyjny danego klienta
	int id_;

	/// stan klienta - true oznacza, że odebrano nagłówek i trwa oczekiwanie
	/// na pozostała część pakietu, false oznacza oczekiwanie na nagłówek
	bool header_found_;

	/// wskaźnik na odbierany pakiet
	//Packet * packet;

	/// adres
	QHostAddress addr_;

	/// port, na którym nawiązane jest połączenie
	quint16 port_;

	/// port, na którym dany klient nasłuchuje na nowe połączenia
	quint16 listening_port_;

	/// flaga kontrolująca działanie wątku danego klienta
	volatile bool active_;

	/// mutex organizujący dostęp do kolejki pakietów
	//mutable QMutex * out_queue_mutex;
	//mutable QMutex * in_queue_mutex;
	Semaphore in_queue_mutex;
	Semaphore out_queue_mutex;


	/// kolejka pakietów wychodzących
	QQueue<Packet *> out_queue;

	/// kolejka pakietów przychodzących
	QQueue<Packet *> in_queue;

private slots:
	/**
	 * Zdarzenie wywoływane w momencie nadejścia nowych danych na gniazdo
	 * związane z danym klientem. Jeśli danych jest odpowiednio dużo (przyszedł
	 * cały pakiet) to sygnalizowane jest odebranie pakietu.
	 **/
	void onNewData();

	/**
	 * Zdarzenie wywoływane w momencie, gdy do kolejki wstawiony został
	 * nowy pakiet do wysłania.
	 **/
	void onSend();

public slots:
	/**
	 * Gniazdo się rozłączyło
	 **/
	void onDisconnected();

signals:
	/**
	 * Sygnał emitowany po odebraniu nowego pakietu.
	 * Emitowany jest po całkowitym odebraniu pakietu,
	 * dla kazdego pakietu emitowany jest oddzielny sygnał.
	 *
	 * @param client wskaźnik na klienta (połączenie) z którego został odebrany pakiet
	 **/
	void newPacket(RemoteClient * client);

	/**
	 * Zasygnalizowanie sytuacji, w której połączenie na gnieździe
	 * klienta zostaje utracone. W reakcji na to należy klienta delikatnie
	 * zatłuc i usunąć z listy.
	 **/
	void clientDied(RemoteClient * client);

	/**
	 * Komunikat wysyłany do systemu logującego zdarzenia.
	 *
	 * @param msg treść komunikatu
	 * @param type typ komunikatu
	 **/
	void newMsg(QString msg, int type);
};

} // namespace Network

#endif // REMOTECLIENT_H
